<?php
require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'wsr.inc.php';
require_once MORIARTY_DIR . "simplegraph.class.php";
require_once MORIARTY_DIR . 'store.class.php';

class TripleCache {   // Nad writes
	var $source;
	//var $store;
	var $loaded_entities;  // array listing (partially)loaded entities
	var $meta;             // array listing meta information about entities, predicate fan-in/out, which predicates loaded
	var $graph;
	
	function __construct(&$source) {
		$this->source = &$source;
		//$store_uri = 'http://api.talis.com/stores/bbc-backstage';
		//$this->store = new Store($store_uri);
		$this->loaded_entities = array();
		$this->meta = array('maxfanout'=>array(),'fan-out'=>array(),'fan-in'=>array());
		$this->graph = new SimpleGraph();
	}
	
	function add_graph($new_graph) {
		return $this->graph->add_graph($new_graph);
	}

	function get_graph() {
		return $this->graph;
	}

	function get_meta() {
		return $this->meta;
	}

   /*  $fresh_entities is a list of entities that need to be loaded
    *                 into the cache
    *
    *  in paper talk baout dfferent kinds of loading, but for
    *  for this simplest will be effectively a 'describe' for each
    *  except
    *      (1)  only need the entity-entity triples, not properties
    *      (2)  if using multiple stores and the link between stores
    *            is through shared values rather than URIs then need
    *            to mange this indirection
	*      (3)  do need BOTH directions, that is object lookup as well as subject
	*/
	/*
    function update($fresh_entities) {
		$triples_were_added = false;
		$new_entities = $this->unseen($fresh_entities);
		foreach ( $new_entities as $entity ) {
			if ( $this->_load_one($entity) ) {
				$triples_were_added = true;
			}
		}
		//echo "\n" . count($new_entities) . "\n";
		return $triples_were_added;
    }
	*/
    function update($fresh_entities) {
		$fanout_fill = array_fill( 0, count($fresh_entities), 1000  );
		$entity_fanout_info = array_combine($fresh_entities,$fanout_fill);
		return $this->update_with_fanout($entity_fanout_info);
    }
	
    function update_with_fanout($entity_fanout_info) {
		$triples_were_added = false;
		$loadable_entity_info = $this->loadable($entity_fanout_info);
		foreach ( $loadable_entity_info as $entity => $fanout ) {
			if ( $this->_load_one($entity,$fanout) ) {
				$triples_were_added = true;
			}
		}
		//echo "\n" . count($loadable_entity_info) . "\n";
		return $triples_were_added;
    }
	
    function unseen($fresh_entities) {
		return array_diff($fresh_entities,$this->loaded_entities);
    }
	
    function loadable($maxfanout_info) {
		$loadable = array();
		foreach ( $maxfanout_info as $entity => $maxfanout ) {
			//echo "compare $maxfanout > get_maxfanout($entity) = ".$this->get_maxfanout($entity)." \n";
			if ( $maxfanout > $this->get_maxfanout($entity) ) {
				$loadable[$entity] = $maxfanout;
			}
		}
		return $loadable;
    }
	
	function get_maxfanout($entity) {
		if ( !array_key_exists($entity,$this->meta['maxfanout']) ) {
			return 0;
		} else {
			return $this->meta['maxfanout'][$entity];
		}
	}
	
	function set_maxfanout($entity,$fanout) {
		//if ( !array_key_exists($entity,$this->meta)['maxfanout'] ) {
		//	$this->meta[$entity] = array();
		//}
		$this->meta['maxfanout'][$entity] = $fanout;
	}
	
	
	/*
	 *  Alan added based on Nad's code, but I think describe is one way ont (subject index)
	 *  if so then this also needs to have lookup the other way round
	 */
	protected function _load_one($entity,$maxfanout=1000) {
		$loaded_already = array_key_exists( $entity, $this->loaded_entities );
		if ( $loaded_already && $this->get_maxfanout($entity) >= $maxfanout ) return false;
		//$query = 'describe ?s where { ?s a <' . $entity . '> } LIMIT 10';
		//$new_graph =  $this->store->get_sparql_service()->graph_to_simple_graph($query);
		$query = 'describe <' . $entity . '> ';
		echo "+";
		//echo $query ."\n";
		//$new_graph =  $this->store->get_sparql_service()->graph_to_simple_graph($query);
		$new_graph =  $this->source->do_query($query);
		
		$fanout_meta = $this->get_fanout($new_graph);
		$new_graph = $this->filter_fanout($new_graph,$maxfanout);
		$triples_were_added = $this->graph->add_graph($new_graph);
		
		$query = 'construct { <'. $entity .'> ?p ?relatedObject  }' .   // NOTE: constrct inverse graph initially
		         'where { ?relatedObject ?p <'. $entity .'> }' .
				 'limit 100';
		echo "-";
		//echo $query ."\n";
		//$new_graph =  $this->store->get_sparql_service()->graph_to_simple_graph($query);
		$new_graph =  $this->source->do_query($query);
		$fanin_meta = $this->get_fanout($new_graph);
		$new_graph = $this->filter_fanout($new_graph,$maxfanout);
		$new_graph = $new_graph->make_inverse_graph();
		$triples_were_added |= $this->graph->add_graph($new_graph);
		//echo "========= TripleCache:_load_one  NEW GRAPH ========\n";
		//print_r( $new_graph );
		//echo "=================\n";

		$this->set_maxfanout($entity,$maxfanout);
		$this->merge_meta('fan-out',$fanout_meta);
		$this->merge_meta('fan-in',$fanin_meta);
		$this->loaded_entities[] = $entity;
		return $triples_were_added;
	}
	
	function filter_fanout($graph,$maxfanout) {
		$index = $graph->get_index();
		$filtered = array();
		foreach( $index as $s => $predicates ) {
			$subject_filtered = array();
			foreach ( $predicates as $p => $objects ) {
				if ( count($objects) <= $maxfanout ) {
					$subject_filtered[$p] = $objects;
				}
			}
			if ( count($subject_filtered) > 0 ) {
				$filtered[$s] = $subject_filtered;
			}
		}
		return new AxGraph( $filtered );
	}
	
	function get_fanout($graph) {
		$index = $graph->get_index();
		$gmeta = array();
		foreach( $index as $s => $predicates ) {
			$subject_meta = array();
			foreach ( $predicates as $p => $objects ) {
				$subject_meta[$p] = count($objects);
			}
			$gmeta[$s] = $subject_meta;
		}
		return $gmeta;
	}
	
   /*
    *  @param $prop is the property name to merge on (e.g. 'fan-out')
    *  @param $meta is a multi-level mapping subject => values
	*                values may be simple (e.g. '37'), or an array (e.g. predicate => fanout)
    *
	*/
	function merge_meta($prop,$meta) {
		foreach ( $meta as $s => $values ) {
			if ( array_key_exists($s,$this->meta[$prop]) ) {
				if ( is_array( $this->meta[$prop][$s]  && is_array($values) ) ) {
					$this->meta[$prop][$s] = array_merge( $this->meta[$prop][$s],$values ); 
				} else {
					$this->meta[$prop][$s] = $values;
				}
			} else {
				$this->meta[$s] = array ( $prop => $values );
			}
		}
	}

    /*
		construct
		{
		  ?relatedObject ?p <http://www.bbc.co.uk/programmes/b00hxnfs#programme>
		}
		where
		{
		  ?relatedObject ?p <http://www.bbc.co.uk/programmes/b00hxnfs#programme>
		}     */


}
   

?>